<!DOCTYPE HTML>
<html lang="en">
<head>
<title>ADSI, Exchange, and Python</title>
</head>
	<body bgcolor="FFFFFF">
	<!-- beginning of leaf header-->

	<table border=0  cellpadding=0 cellspacing=0 width=100%>
	<tr><td valign=middle width="100%" bgcolor="#99ccff">
	<font face="sans-serif" size="+1" color="#111111">
	&nbsp;&nbsp;&nbsp;ADSI, Exchange, and Python</font>
	</td>
	</tr>
	</table>
	<p>&nbsp</p>
	<!-- end of leaf content-->
<!-- INDEX BEGIN -->

<ul>

	<li><a href="#SUMMARY">SUMMARY</A></LI>
	<ul>
		<li><a href="#Introduction">Introduction</a></li>
		<li><a href="#User">User Account Management</a></li>
		<ul>
			<li><a href="#add">Adding a user</A></li>
			<li><a href="#get">Getting/Modifying user info</a></li>
			<li><a href="#delete">Deleting a User</a></li>
		</ul>
		<li><a href="#Distribution List">Distribution List</a></li>
		<ul>
			<li><a href="#dadd">Adding to a list</a></li>
			<li><a href="#dlist">Recursively listing all unique members</a></li>
		</ul>
                <li>a href="#Conclusion">In Conclusion</a>
	</ul>

	<li><a href="#Further Info">Further Info</a></li>
	<li><a href="#Author">Author</a></li>
</ul>
<!-- INDEX END -->

<hr>
<h1><a name="SUMMARY">SUMMARY</a></h1>
<p>
Python's adsi access works really well with Exchange (late or early binding since you can read microsoft's type library).
To get started, you will need to download adsi from microsoft:
<a href="https://www.microsoft.com/windows/server/Technical/directory/adsilinks.asp">Microsoft ADSI</a>.
Microsoft has documentation for using languages other than python in the sdk.
</p>

<hr>
<h2><a name="Introduction">Introduction</a></h2>
<p>

Before doing anything else you need to go through the next two steps:
<table border=2 cellpadding=4 cellspacing=1>
<caption align=top><strong>Acquiring ADSI object</strong></caption>
<tr>
<th>Task</th><th>Description</th>
</tr>
<tr>
<td>Create Global Providers Object</td><td>adsi = win32com.client.Dispatch('ADsNameSpaces')</td>
</tr>
<tr>
<td>Create LDAP Provider Object</td><td>ldap = adsi.getobject("","LDAP:")</td>
</tr>
</table>


<br>Now you have to decide how you want to access the exchange server.
<br>I have chosen to authenticate, in which case you need to use OpenDSObject()

<table border=2 cellpadding=4 cellspacing=1>
<CAPTION align=top><STRONG>Method of access</STRONG></CAPTION>
<tr>
<th>Task</th><th>Description</th>
</tr>
<tr>
<td>Specify Login and Domain</td><td>logon_ex='cn=wilma, dc=bedrock'</td>
</tr>
<tr>
<td>Specify password</td><td>password='dino'</td>
</tr>
<tr>
<td>Login to Server</td><td>myDSObject = ldap.OpenDSObject(ex_path,logon_ex,password,0)</td>
</tr>
</table>

<br>
<strong>Note</strong> -- the fourth argument to opendsobject has various options for how to authenticate.
For example, if you use 1 instead of zero, it should either use NTLM or Kerberos for authentication.
For more information, check out: <a href="http://www.microsoft.com/windows/server/Technical/directory/adsilinks.asp">OpenDSObject</a>

<p>
The ex_path in the above example specifies the resource you are trying to access.  For example:
</p>
<table border=2 cellpadding=4 cellspacing=1>
<caption align=top><strong>Types of paths</strong></caption>
<tr>
<th>Task</th><th>Description</th>
</tr>
<tr>
<td>Specific User</td><td>ex_path="LDAP://server/cn=fredflintsone,cn=Recipients,ou=rubble,o=bedrock"</td>
</tr>
<tr>
<td>Mailing List</td><td>ex_path="LDAP://server/cn=bedrock,cn=Recipients,ou=rubble,o=bedrock"</td>
</tr>
<tr>
<td>All Recipients</td><td>ex_path="LDAP://server/cn=Recipients,ou=rubble,o=bedrock"</td>
</tr>
</table>

</p>

<hr>
<h2><A NAME="User">User Account Management</A></h2>
<h3><a name="add">Adding a user to exchange</A></h3>
<code>
# Adding a new account to exchange is simple except for one thing.
# You need to associate an NT account with an exchange account.
# To do so at this point requires some c++ to produce some hex SID
# and trustee information that adsi can use.
# At this point assume we have C++ magic
#
# Note we are accessing Recipients directly now
ex_path="LDAP://server/cn=Recipients,ou=rubble,o=bedrock"
logon_ex='cn=wilma,dc=bedrock'
password='dino'
myDSObject = ldap.OpenDSObject(ex_path,logon_ex,password,0)

newobj = myDSObject.create("OrganizationalPerson", "cn=betty")
newobj.put('MailPreferenceOption', 0)
# etc . . . add whatever else you want. There are a few required fields.
# Now the part to get exchange associated with NT
# The Magic is here
import win32pipe
assoc_nt=win32pipe.popen('getsid bedrock\\fredflint')
nt_security=win32pipe.popen('gettrustee bedrock\\fredflint')
newobj.put('NT-Security-Descriptor',assoc_nt)
newobj.put('NT-Security-Descriptor',nt_security)

newobj.SetInfo
</code>

<h3><a name="get">Getting/Modify user info</a></h3>

<code>
ex_path="LDAP://server/cn=fredflint,cn=Recipients,ou=rubble,o=bedrock"
myDSObject = ldap.OpenDSObject(ex_path,logon_ex,password,0)
myDSObject.Getinfo()
# To access a user's data try:
attribute = myDSObject.Get('Extension-Attribute-1')
print(attribute)
# To modify a user try:
myDSObject.Put('Extension-Attribute-1','barney was here')
myDSObject.Setinfo()
</code>
Comments

<strong>Note</strong> -- To make any changes permanent setinfo is required.


<h3><a name="delete">Deleting a user from exchange</a></h3>

<code>
#Here we connect to Recipients and then
#delete a user
#This is a more complete example.
#data is a dictionary that contains info
#that may be dynamic like the domain,
#admin login, or exchange server
#notice I am using a try/except clause here
#to catch any exceptions
try:
  #ADSI here
  # Create the Global Providers object
  logon_ex='cn='+data['NT_admin']+', dc='+data['NT_domain']+',cn=admin'
  ex_list_path="LDAP://"+data['EX_site_srv']+"/cn=Recipients,ou="\
  +data['ou']+",o="+data['o']
  adsi = win32com.client.Dispatch('ADsNameSpaces')
  #
  # Now get the LDAP Provider object
  ldap = adsi.getobject("","LDAP:")
  dsobj = ldap.OpenDSObject(ex_list_path,logon_ex,data['NT_password'],0);
  dsobj.Getinfo()
  dsobj.Delete("OrganizationalPerson", "cn="+login)
  dsobj.Setinfo()
except:
  print("Error deleting "+login, sys.exc_type , sys.exc_value)
</code>
</p>
<hr>
<h2><a name="Distribution List">Distribution List</a></h2>
<p>
<pre>
<hr>


<h3><a name="dadd">Adding to a distribution list</a></h3>

<code>
# I used putex instead of put because it has more options
# The '3' value means append. The SDK has specific info on it
ex_list_path="LDAP://"+server+"/cn="+list+",cn=Recipients,ou="+ou+",o="+o
dsobj = ldap.OpenDSObject(ex_list_path,logon_ex,password,0);
dsobj.Getinfo()
list_member='cn='+user+',cn=Recipients,ou='+ou+',o='+o
append_list=[list_member]
dsobj.putEx(3,'Member',append_list);
dsobj.SetInfo()
</code>


<h3><a name="dlist"></A>Recursively listing all unique members of a distribution list</h3>

<code>
#This function looks for all Organizational persons to add to a dictionary
#If it gets a groupOfNames, it needs to parse that and call the function again
#to get the members of the groupOfNames
def getmembers(path=''):
  user_dict={}
  logon_ex='cn=fred, dc=bedrock'
  password='dino'
  server='flintstone'
  ldap = win32com.client.Dispatch('ADsNameSpaces').getobject("","LDAP:")
  dsobj = ldap.OpenDSObject(path,logon_ex,password,0)
  dsobj.Getinfo()
  if dsobj.Class=='organizationalPerson':
      user_dict[dsobj.cn.capitalize()]=dsobj.uid
  elif dsobj.Class=='groupOfNames':
      for i in dsobj.Members():
              if i.Class=='organizationalPerson':
                  user_dict[i.cn.capitalize()]=i.uid
              elif type(i.member)==types.TupleType:
                  for j in i.member:
                      newpath='LDAP://'+server+'/'+j
                      getmembers(newpath)
              elif type(i.member)==types.StringType:
                  newpath='LDAP://'+server+'/'+i.member
                  getmembers(newpath)
  elif dsobj.Class=='Remote-Address':
      User_dict[dsobj.cn.capitalize()]=dsobj.uid
  elif  dsobj.Class=='Public-Folder':
      pass
  else:
      print("skipped",dsobj.Class,dsobj.uid)
  return user_dict


</code>
</p>
<p>

<hr>
<h2><a name="Conclusion">In Conclusion</a></h2>
Microsoft's ADSI allows one to manage exchange w/out having to resort to the lower-level APIs.
Python has no trouble accessing Microsoft's ADSI to help simplify user management.
</p>
<p>

<hr>
<h1><a name="Further Info">Further Info</a></h1>
<p>
<ul>
<li><a href="http://msdn.microsoft.com">Microsoft MSDN references</a>
<li><a href="http://www.microsoft.com/windows/server/Technical/directory/adsilinks.asp">Microsoft ADSI</a>
<li><a href="http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/adsi/if_core_3uic.htm">Microsoft MSDN ADSI reference</a>
<li>Relevant Python libraries: win32com.client
</ul>
</p>
<hr><h1><a name="Author">Author</a></h1>
John Nielsen, <a href="mailto:jn@who.net,">jn@who.net</A>
<br>-- Have a great time with programming with python!
		<!-- beginning of leaf footer-->
		<p>&nbsp;</p>
		<table border=0  cellpadding=0 cellspacing=0 width=100%>
		<tr>
			<td valign=middle
			bgcolor="#99ccff"> <font face="sans-serif" size="+1"
			color="#111111">&nbsp;&nbsp;&nbsp;ADSI, Exchange, and Python</font>
			</td>
		</tr>
		</table>
		<!-- end of leaf footer-->
</body>
</html>
